Kustomizeのreplacementsを使ってコンテナイメージタグ値で環境変数を作成する
やりたいこと
Podのコンテナで使われているコンテナイメージのタグ値をつかって、そのPodに環境変数を設定したいです。
以下のように同じ値をそれぞれの設定箇所に書けばシンプルに実現できますが、保守性の観点から同じ値を複数箇所に書くのは避けたいのです。(絶対に環境変数値の更新を忘れる)
--- apiVersion: apps/v1 kind: Deployment metadata: name: sample spec: replicas: 1 selector: matchLabels: app: sample template: metadata: labels: app: sample spec: containers: - name: sample image: nginx:1.20 env: - name: HOGE value: "1.20"
1箇所書けばイメージタグ値にも環境変数値にも使われる様になればよいので、以下でも構いません。
- 環境変数を設定して、イメージタグ指定時にその環境変数を参照する
- どこかで変数的なものを設定して、環境変数もイメージタグもその変数を参照する
解決方法: Kustomizeのreplacementsを使う
replacementsはその名の通り、特定のリソースの特定のフィールド値を、1個以上のターゲットフィールドの値として使う(置換する)機能です。
今回はこの機能を使ってイメージタグ値をもとに環境変数を定義します。
先に完成形をお見せします。
--- apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - applications/deployment.sample.yaml replacements: - source: kind: Deployment name: sample fieldPath: spec.template.spec.containers.[name=sample].image options: delimiter: ":" index: 1 targets: - select: kind: Deployment name: sample fieldPaths: - spec.template.spec.containers.[name=sample].env.[name=HOGE].value
--- apiVersion: apps/v1 kind: Deployment metadata: name: sample spec: replicas: 1 selector: matchLabels: app: sample template: metadata: labels: app: sample spec: containers: - name: sample image: nginx:1.20 env: - name: HOGE value: "(overwrite with replacements)"
解説します。
kustomization.yaml
のreplacements
フィールド以下に置換設定を書き連ねていきます。配列型で記述していきます。- 各配列要素は
source
とtarget
フィールドが必要です。 source
以下に参照元のフィールドの情報を定義します。fieldPath
フィールドで書いているように途中配列型になっている箇所については、どの要素なのか絞り込む必要があります。そういう場合は角括弧([]
)で囲って要素内のハッシュをキー=バリュー
の形で指定します。- 今回参照したい値は
image
フィールドの一部分、タグの部分だけです。image
フィールドは(イメージ名):(タグ)
という構造になっているため、タグ値を取りたい場合は:
で区切った場合の2つ目の要素=インデックス値は1の部分を取得しています。
targets
に置換先の情報を定義します。複数個置換先に指定できるのでこのフィールド以下も配列になります。
ちなみにreplacementsの記述は別ファイル化もできます。
--- apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - applications/deployment.sample.yaml replacements: - path: replacements/replacement.sample.yaml
source: kind: Deployment name: sample fieldPath: spec.template.spec.containers.[name=sample].image options: delimiter: ":" index: 1 targets: - select: kind: Deployment name: sample fieldPaths: - spec.template.spec.containers.[name=sample].env.[name=HOGE].value
エラー発生
検証実装は上記でOKだったのですが、本番のコードに移植したところエラーになりました。本番のコードは複数環境(dev,stg,prod)に対応できるように以下のようなディレクトリ構成にしてoverlayを使っています。
. ├── base │ ├── applications │ │ └── deployment.sample.yaml │ └── kustomization.yaml └── env ├── dev │ └── kustomization.yaml ├── prod │ └── kustomization.yaml └── stg └── kustomization.yaml
./env/(dev|stg|prod)/
以下で環境毎の差分を定義する感じです。例えば以下はdev環境だけnginxイメージのタグをfugaに変えています。
--- apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - ../../base images: - name: nginx newTag: fuga
このコードに対してreplacementsのコードを追加します。replacementsの処理自体は環境差異がないので、./base/kustomization.yaml
に追記しました。
--- apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - applications/deployment.sample.yaml + replacements: + - source: + kind: Deployment + name: sample + fieldPath: spec.template.spec.containers.[name=sample].image + options: + delimiter: ":" + index: 1 + targets: + - select: + kind: Deployment + name: sample + fieldPaths: + - spec.template.spec.containers.[name=sample].env.[name=HOGE].value
しかし、このやり方だと思ったような結果になりませんでした。環境変数HOGEの値は1.20のままです。これはおそらく以下のような順序で処理が走るからだと思われます。
- 環境変数HOGEの値はDeploymentのマニフェストファイルに書かれている通り
(overwrite with replacements)
でスタート - baseのレイヤーでの処理。replacementsによって タグ値=1.20で環境変数HOGEが上書きされる
- devのレイヤーでの処理。newTagによってnginxのタグ値が
fuga
に上書きされる - devレイヤーにreplacementsは無いので、環境変数HOGEの値が再度上書きされることはない
うまく行かなかったので、前述の./base/kustomization.yaml
の修正は取りやめ、./env/(dev|stg|prod)/kustomization.yaml
を修正することにしました。
--- apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - ../../base images: - name: nginx newTag: fuga + replacements: + - path: ../../base/replacements/replacement.sample.yaml
そしてbase以下にreplacements用のファイルを置きます。
source: kind: Deployment name: sample fieldPath: spec.template.spec.containers.[name=sample].image options: delimiter: ":" index: 1 targets: - select: kind: Deployment name: sample fieldPaths: - spec.template.spec.containers.[name=sample].env.[name=HOGE].value
しかし、このコードに対して kustomize build env/dev/ | kubectl apply -f -
するとエラーになりました。
Error: trouble configuring builtin ReplacementTransformer with config: ` Replacements: - path: ../../base/replacements/replacement.sample.yaml `: security; file './base/replacements/replacement.sample.yaml' is not in or below './env/dev'
エラー解消方法
これは、resources
など一部機能を除き、ルートの kustomization.yaml、今回の例だと./env/dev/kustomization.yaml
があるディレクトリ外のファイルを読み込もうとするとエラーになるという判定に引っかかりました。この判定を無効化するには、kustomize build
コマンドにフラグを追加します。私が今回使っているVersion4系だと以下です。
% kustomize build --load-restrictor LoadRestrictionsNone env/dev/
Version3系だと以下だそうです。
% kustomize build --load_restrictor none env/dev/
以上で要件の実現ができました?
参考情報
失敗した方法達
replacementsに至るまでの色々試行錯誤した過程も残しておきます。まだまだ全然マニフェストファイルの書き方わからない…
imageで環境変数を参照
$()
で囲めば環境変数を参照できるので、それをimage欄で試してみました。
--- apiVersion: apps/v1 kind: Deployment metadata: name: sample spec: replicas: 1 selector: matchLabels: app: sample template: metadata: labels: app: sample spec: containers: - name: sample image: nginx:$(HOGE) env: - name: HOGE value: "1.20"
結果、applyはできましたが変数展開されませんでした。imageでは環境変数使えないということなんでしょうか。
[ { "env": [ { "name": "HOGE", "value": "1.20" } ], "image": "nginx:$(HOGE)", "imagePullPolicy": "IfNotPresent", "name": "sample", "resources": {}, "terminationMessagePath": "/dev/termination-log", "terminationMessagePolicy": "File", "volumeMounts": [ { "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", "name": "default-token-lt8qm", "readOnly": true } ] } ]
参考情報
fieldRef
fieldRef
を使うとlabelやannotationなどに定義した値を参照できるので、annotationでタグ値を定義して、imageと環境変数欄でそれらを参照してみました。以下の例はタグ値だけでなくてイメージ名までまとめてannotation化してみた例です。
--- apiVersion: apps/v1 kind: Deployment metadata: namespace: kcr-cs name: sample spec: replicas: 1 selector: matchLabels: app: sample template: metadata: labels: app: sample annotations: image: nginx:1.20 spec: containers: - name: sample image: valueFrom: fieldRef: fieldPath: metadata.annotations['image'] env: - name: HOGE valueFrom: fieldRef: fieldPath: metadata.annotations['image']
これはエラーになりました。環境変数はこの方法でOKなのですがimageはダメなようです。
% kustomize build . | kubectl apply -f - Error: wrong Node Kind for expected: ScalarNode was MappingNode: value: {valueFrom: fieldRef: fieldPath: metadata.annotations['image']} error: no objects passed to apply
参考情報
- Podフィールドを環境変数の値として使用する | 環境変数によりコンテナにPod情報を共有する | Kubernetes
- EnvVarSource v1 core | Kubernetes API Reference Docs
resourceFieldRef
resourceFieldRef
を使えばコンテナの情報を参照できる!のですが、参照できる情報が限られており、以下のみです。今回の要件には合いませんでした。
- limits.cpu
- limits.memory
- limits.ephemeral-storage
- requests.cpu
- requests.memory
- requests.ephemeral-storage
参考情報
- コンテナフィールドを環境変数の値として使用する | 環境変数によりコンテナにPod情報を共有する | Kubernetes
- EnvVarSource v1 core | Kubernetes API Reference Docs
vars/varReference
ググるとKustomizeのVars/VarReference機能を使って似たような要件を実現している情報が見つかります。が、公式ドキュメントにvarsはdeprecatedにする計画があり、可能な限り早くreplacementsに置き換えることが推奨と書かれていました。そのため深く調査はせず、replacementsの調査に進みました。
参考情報
sed
kustomizeの一つ前のレイヤで動的にマニフェストファイルを作ろうという案です。
例えば以下のようなテンプレートを作っておきます。
--- apiVersion: apps/v1 kind: Deployment metadata: name: sample spec: replicas: 1 selector: matchLabels: app: sample template: metadata: labels: app: sample spec: containers: - name: sample image: nginx:{{IMAGE_TAG}} env: - name: HOGE value: "{{IMAGE_TAG}}"
そして以下のようなコマンドを実行します。
% sed -e 's/{{IMAGE_TAG}}/fuga/g' ./applications/deployment.sample.yaml.tmpl> ./applications/deployment.sample.yaml
すると{{IMAGE_TAG}}
部分が置換されたマニフェストファイルができあがります。
--- apiVersion: apps/v1 kind: Deployment metadata: name: sample spec: replicas: 1 selector: matchLabels: app: sample template: metadata: labels: app: sample spec: containers: - name: sample image: nginx:fuga env: - name: HOGE value: "fuga"
これでも要件は実現できますが、もう1レイヤ増えるのがイマイチだなと感じたので今回は採用しませんでした。
私はまだ全然さわったことがないのですが、cdk8sを使うとこういうのが簡単にできるのかもしれません。